import {withLock} from "easylock";
import {Types} from "mongoose";
import {arrayProp, index, InstanceType, ModelType, pre, prop, Ref, Typegoose} from "typegoose";
import {ErrorCode, InternalError} from "../../api/error";
import {LipstickSeries, LipstickSeriesModel} from "./lipstickSeries";
import {Product, ProductModel} from "./Product";

@pre<ProductGroup>('save', async function (next) {

  if (this.level1 && this.level1.length === 0)
    throw Error('Level1 不能为空')
  if (this.level2 && this.level2.length === 0)
    throw Error('Level2 不能为空')
  if (this.level3 && this.level3.length === 0)
    throw Error('Level3 不能为空')
  if (this.level4 && this.level4.length === 0)
    throw Error('Level4 不能为空')

  const pList = []
  let stock = 0
  pList.push(this.level1)
  pList.push(this.level2)
  pList.push(this.level3)
  pList.push(this.level4)
  for (const p of pList) {
    stock += (await ProductModel.findById(p)).stock
  }
  if (this.category === `eggDraw`) {
    if (stock < 10)
      this.state = `off`
  } else if (this.category === `luckyBox`) {
    if (stock < 15)
      this.state = `off`
  }

  await setHighestPrice(this)

  next()
})

export class ProductGroup extends Typegoose {

  @prop({required: true})
  name: string

  @prop({required: true})
  description: string

  @prop({required: true})
  priceInGem: number

  @prop()
  highestPrice: number

  @arrayProp({itemsRef: Product, required: true})
  level1: Array<Ref<Product>>

  @arrayProp({itemsRef: Product, required: true})
  level2: Array<Ref<Product>>

  @arrayProp({itemsRef: Product, required: true})
  level3: Array<Ref<Product>>

  @arrayProp({itemsRef: Product, required: true})
  level4: Array<Ref<Product>>

  @prop({required: true})
  category: 'eggDraw' | 'luckyBox'

  @prop({required: true})
  state: 'on' | 'off' | 'preview' | 'limited'

  @prop({required: true, default: 50})
  sortNum: number

  @prop({required: true, default: 0})
  hotNum: number
}

@pre<PressGroup>(`save`, async function (next) {

  if (this.level1 && this.level1.length === 0)
    throw Error(`鼓励奖 不能为空`)
  if (this.level2 && this.level2.length === 0)
    throw Error(`二等奖 不能为空`)
  if (this.level3 && this.level3.length === 0)
    throw Error(`一等奖 不能为空`)

  next()
})
export class PressGroup extends Typegoose {
  @prop({required: true})
  name: string

  @prop({required: true})
  description: string

  @prop({required: true})
  priceInCoin: number

  @prop({required: true, default: 999})
  priceInFangka: number

  @prop({required: true})
  coverUrl: string

  @arrayProp({itemsRef: LipstickSeries, required: true})
  level1: Array<Ref<LipstickSeries>>

  @arrayProp({itemsRef: LipstickSeries, required: true})
  level2: Array<Ref<LipstickSeries>>

  @arrayProp({itemsRef: LipstickSeries, required: true})
  level3: Array<Ref<LipstickSeries>>

  @prop({required: true, default: 0})
  stock: number

  @prop({required: true})
  state: 'on' | 'off' | 'preview' | 'limited'

  @prop({required: true, default: () => new Date()})
  createAt: Date

  @prop({required: true, default: () => new Date()})
  updateAt: Date

  @prop({required: true, default: 50})
  sortNum: number

  @prop({required: true, default: 0})
  hotNum: number
}

@index({updateAt: -1})
export class PressGroupIncome extends Typegoose {
  @prop({ref: PressGroup, required: true, unique: true})
  pressGroup: Ref<PressGroup>

  @prop({required: true})
  income: number

  @prop({required: true, default: () => new Date()})
  updateAt: Date
}

export const ProductGroupModel: ModelType<ProductGroup>
  = new ProductGroup().getModelForClass(ProductGroup)
export const PressGroupModel: ModelType<PressGroup>
  = new PressGroup().getModelForClass(PressGroup)
export const PressGroupIncomeModel: ModelType<PressGroupIncome>
  = new PressGroupIncome().getModelForClass(PressGroupIncome)

async function setHighestPrice(productGroup) {
  try {
    const levels = [1, 2, 3, 4]
    let highestPrice = 0
    for (const i of levels) {
      const products = productGroup[`level${i}`]
      if (products) {
        for (const product of products) {
          const p = await ProductModel.findById(product)
          if (p)
            highestPrice = highestPrice > p.marketPrice ? highestPrice : p.marketPrice
          else
            throw InternalError(`NO_SUCH_PRODUCT`)
        }
      }
    }
    productGroup.highestPrice = highestPrice
  } catch (e) {
    return e
  }
}

export async function getAfterReduceMostStockProductBySeries(lipstickSeriesId: Types.ObjectId):
  Promise<{ ok: boolean, data: { productId: Types.ObjectId, message: ErrorCode } }> {

  return await withLock(lipstickSeriesId.toString(), async () => {

    try {
      const ls = await LipstickSeriesModel.findById(lipstickSeriesId)
        .populate(`products`)

      if (!ls || !ls.onStock)
        throw InternalError("NO_ON_STOCK")

      const products = [] as Array<InstanceType<Product>>
      for (const p of ls.products as Array<InstanceType<Product>>) {
        const product = await ProductModel.findById(p._id)
        if (product)
          products.push(product)
      }
      if (products.length <= 0)
        throw InternalError("NO_SUCH_PRODUCT")

      products.sort((a, b) => {
        return b.stock - a.stock
      })

      const {ok, successList} = await deleteProductStockSafely([products[0]._id])
      if (!ok || successList.length === 0) {
        await LipstickSeriesModel.findByIdAndUpdate(lipstickSeriesId, {$set: {state: `off`}})
        throw InternalError("NO_ON_STOCK")
      }
      await ls.save()
      return {
        ok: true,
        data: {
          productId: successList[0]
        }
      }
    } catch (e) {
      return {
        ok: false,
        data: {
          message: e.message
        }
      }
    }
  })
}

export interface PreProducts {
  level1: Level,
  level2: Level,
  level3: Level,
}

interface Level {
  _id: Types.ObjectId,
  seriesId: Types.ObjectId
}

export async function getProductsFromPressGroupLevels
(pressGroupId: Types.ObjectId):
  Promise<{ ok: boolean, data: { result: PreProducts, message: ErrorCode } }> {

  return await withLock(pressGroupId.toString(), async () => {

    try {
      const pressGroup = await PressGroupModel.findById(pressGroupId)
        .populate(`level1`)
        .populate(`level2`)
        .populate(`level3`)

      if (!pressGroup || pressGroup.state !== 'on')
        throw InternalError("NO_ON_STOCK")

      const result = {
        level1: null,
        level2: null,
        level3: null,
      }
      const nums = [1, 2, 3]
      for (const num of nums) {
        const series = {_id: null, seriesId: null}
        let stock = 0
        for (const it of pressGroup[`level${num}`]) {
          const s = await LipstickSeriesModel.findById(it._id).populate(`products`).lean()
          if (!s || !s.onStock) continue

          const ps = await ProductModel.find({
              _id: {$in: s.products},
              state: {$in: [`on`, `limited`, `preview`]},
              stock: {$gt: 0}
            }
          ).sort({stock: -1}).lean()
          if (ps.length > 0 && ps[0]._id && ps[0].stock > stock) {
            stock = ps[0].stock
            series._id = ps[0]._id
            series.seriesId = s._id
          }
        }
        if (series._id)
          result[`level${num}`] = series
      }
      if (!result.level1 || !result.level2 || !result.level3) {
        await PressGroupModel.findByIdAndUpdate(pressGroupId, {$set: {state: `off`}})
        throw InternalError("NO_ENOUGH_STOCK")
      }
      const {ok: okay} = await deleteProductStockSafely(
        [result.level1._id, result.level2._id, result.level3._id])
      if (!okay) {
        await PressGroupModel.findByIdAndUpdate(pressGroupId, {$set: {state: `off`}})
        throw InternalError("NO_ENOUGH_STOCK")
      }

      return {
        ok: true,
        data: {
          result,
          message: null
        }
      }
    } catch (e) {
      return {
        ok: false,
        data: {
          result: null,
          message: e.message
        }
      }
    }
  })
}

export async function deleteProductStockSafely(productIds: Types.ObjectId[]):
  Promise<{ ok: boolean, successList: Types.ObjectId[], failList: Types.ObjectId[] }> {

  const result = {
    ok: false,
    successList: [],
    failList: []
  }

  for (const productId of productIds) {
    if (!productId) {
      console.log(`收到非法productId`)
      return result
    }
  }

  for (const productId of productIds) {
    await withLock(productId.toString(), async () => {
      const p = await ProductModel.findById(productId)
      if (!p || p.stock <= 0 || p.state !== 'on') {
        result.failList.push(productId)
        return
      }

      p.stock--
      result.successList.push(productId)
      await p.save()
    })
  }

  if (result.failList.length > 0) {
    for (const productId of result.successList) {
      await ProductModel.findByIdAndUpdate(productId, {$inc: {stock: 1}})
    }
    result.successList = []
    return result
  }

  result.ok = true
  return result
}
